package com.hlkj.baojin.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hlkj.baojin.constant.BaojinConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;


/**
 * redis配置
 * #Cacheable将查询结果缓存到redis中，（key=”#p0”）指定传入的第一个参数作为redis的key。 @Cacheable(keyGenerator = "wiselyKeyGenerator")//使用自定义key
 * #CachePut，指定key，将更新的结果同步到redis中           @CachePut(key = "#id + '_'  + #city")//刷新缓存
 * #CacheEvict，指定key，删除缓存数据，allEntries=true,方法调用后将立即清除缓存
 *
 * @author lzx
 **/
@Configuration
//@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {


    static final Logger logger = LoggerFactory.getLogger(RedisConfiguration.class);

    //    //缓存管理器
//    @Bean
//    public CacheManager cacheManager(RedisConnectionFactory factory) {
//        RedisCacheManager cacheManager = RedisCacheManager.builder(factory).build();
//        cacheManager.setTransactionAware(true);
//        return cacheManager;
//    }
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(3600)) // 设置缓存有效期一小时
                .disableCachingNullValues() //禁止缓存Null对象. 这个识需求而定.
                .computePrefixWith(s -> BaojinConstant.REDIS_KEY) //此处定义了cache key的前缀, 避免公司不同项目之间的key名称冲突.
                .withConversionService(createConversionService());


        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration).build();
    }

    //自定义缓存key生成策略
    @Bean
    public KeyGenerator keyGenerator() {
        return RedisConfiguration::generate;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    private static Object generate(Object target, Method method, Object... params) {
        StringBuffer sb = new StringBuffer().append("[className]:").append(target.getClass().getName()).append("  [methodName]:").append(method.getName()).append("  [params]:{");
        Arrays.asList(params).forEach(obj -> sb.append(obj.getClass().getName()).append(":").append(obj.toString()).append(","));
        sb.append("}");
        logger.info("===============redis调用==============================：" + sb.toString());

        //key生成
        StringBuffer key = new StringBuffer().append(target.getClass().getName()).append(method.getName());
        Arrays.asList(params).forEach(o -> key.append(o.getClass().getName()).append(o.toString()));
        return key.toString();
    }

    /**
     * withConversionService(new CacheKeyConversionService())这句注册了一个ConversionService, 作用于Cache对象.
     * 而KeyGenerator会作用@Cacheable注解的方法, 将方法的参数转换为hashCode. 这样的话, Cache对象和@Cacheable就打通了,
     * 可以实现互操作. 即, 可以通过直接操纵Cache对象来读取@Cacheable方法的缓存数据. 这在实际项目中是非常有用的.
     * 但这样做的代价是缓存容易被开发人员滥用, 做架构者不得不考虑这一点.  我也是在开发人员的强烈要求下才加入的,
     * 后续打算通过一个配置默认关闭Cache和@Cacheable之间的互通.
     *
     * @return ConversionService
     */
    @Bean
    public ConversionService createConversionService() {
        return new ConversionService() {
            @Override
            public boolean canConvert(Class<?> aClass, Class<?> aClass1) {
                return true;
            }

            @Override
            public boolean canConvert(TypeDescriptor typeDescriptor, TypeDescriptor typeDescriptor1) {
                return true;
            }

            @Override
            public <T> T convert(Object o, Class<T> aClass) {
                return (T) o;
            }

            @Override
            public Object convert(Object o, TypeDescriptor typeDescriptor, TypeDescriptor typeDescriptor1) {
                return o;
            }
        };
    }


}